# 1.模型约束描述
# 1)输入数据约束：
# 确保输入图像数据的维度和类型正确（例如，32x32x3的RGB图像）。
# 数据应当被归一化到适当的范围（如0到1之间）。
import numpy as np
from keras.datasets.cifar100 import load_data
from matplotlib import pyplot as plt

(train_x, train_y), (test_x, test_y) = load_data()
print(train_x.shape, train_y.shape)
train_x = train_x.reshape(-1, 32, 32, 3) / 255
test_x = test_x.reshape(-1, 32, 32, 3) / 255

from keras.utils import to_categorical

onehot_dim = len(np.unique(train_y))
train_y = to_categorical(train_y, onehot_dim)
test_y = to_categorical(test_y, onehot_dim)

# 2)模型结构约束：
from keras import Sequential, Model, layers, losses, metrics, optimizers, activations


# VGG16架构应准确反映原始VGG16网络的基本思想，即使用多个卷积层和池化层，然后是几个全连接层。
# 每个卷积层后应跟随ReLU激活函数。
# 在全连接层之间使用Dropout来减少过拟合。
# 输出层应使用softmax激活函数以进行多类分类。
class VGG16(Model):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.conv = Sequential([
            layers.Conv2D(16, (3, 3), padding='same', activation='relu'),
            layers.Conv2D(16, (3, 3), padding='same', activation='relu'),
            layers.MaxPooling2D(),
            layers.Conv2D(32, (3, 3), padding='same', activation='relu'),
            layers.Conv2D(32, (3, 3), padding='same', activation='relu'),
            layers.MaxPooling2D(),

            layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
            layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
            layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
            layers.MaxPooling2D(),

            layers.Conv2D(128, (3, 3), padding='same', activation='relu'),
            layers.Conv2D(128, (3, 3), padding='same', activation='relu'),
            layers.Conv2D(128, (3, 3), padding='same', activation='relu'),
            layers.MaxPooling2D(),
        ])
        self.flatten = layers.Flatten()
        self.dense = Sequential([
            layers.Dense(64, activation='relu'),
            layers.Dropout(0.3),
            layers.Dense(64, activation='relu'),
            layers.Dropout(0.3),
            layers.Dense(onehot_dim, activation='softmax')
        ])

    def call(self, inputs, training=None, mask=None):
        inputs = self.conv(inputs)
        inputs = self.flatten(inputs)
        inputs = self.dense(inputs)
        return inputs


# 3)训练过程约束：
# 训练过程应包含验证集，以监控模型在未见过的数据上的性能。
# 允许配置训练参数，如学习率、批处理大小、训练轮次（epochs）等。
# 记录训练过程中的关键指标，评估指标为精度（精确率、召回率、F1）和损失值，以便后续分析和调优。
# 训练过程中，可视化展示训练集、验证集的精度、1oss的变化曲线。
# 简要分析模型的训练效果（分析的内容以注释的形式总结出来）
if __name__ == '__main__':
    model = VGG16()
    model.build(input_shape=(None, 32, 32, 3))
    model.summary()
    model.compile(loss=losses.categorical_crossentropy, optimizer=optimizers.Adam(lr=0.001),
                  metrics=['acc', metrics.Precision(),
                           metrics.Recall(),
                           metrics.F1Score()])
    log = model.fit(train_x, train_y, validation_data=(test_x, test_y), epochs=1, batch_size=128)
    """随着epochs增加，损失越来越低，当epochs 增加到一定数值，损失不变"""
    plt.plot(log.history['precision'])
    plt.plot(log.history['val_precision'])
    plt.show()
    plt.plot(log.history['loss'])
    plt.plot(log.history['val_loss'])
    plt.show()
    plt.plot(log.history['acc'])
    plt.plot(log.history['val_acc'])
    plt.show()

# 4)性能评估约束：
# 在训练完成后，应在测试集上评估模型的性能，包括准确率、损失等。
# 允许生成和可视化训练过程中性能指标的图表，以便直观地了解模型的训练过程。
# 在测试集上使用保存下来的模型文件评估分类模型的性能，包括准确率、
# 混淆矩阵等指标。
    model.save_weights('model.h5')
    new_model = VGG16()
    new_model.build(input_shape=(None, 32, 32, 3))
    new_model.load_weights('model.h5')
    new_model.compile(loss=losses.categorical_crossentropy, metrics=['acc'])
    new_model.evaluate(test_x, test_y)
# 5)模型优化约束：
# 选择一种优化方法对模型进行进一步优化(如调整学习率、使用正则化、剪枝等），并分析优化后的结果。
    """经过我们调试，发现最优学校率是多少"""
# 6)模型保存和加载约束：
# 模型训练完成后，应能够保存模型权重，以便将来进行预测或进一步训练。
# 应能够加载已保存的模型权重，以便在不需要重新训练的情况下进行预测。将训练好的模型权重保存到文件model.h5中。
# 7)错误处理和日志记录约束：
# 在数据加载、模型训练、性能评估等过程中，应妥善处理可能出现的错误和异常。
# 记录详细的日志信息，以便在出现问题时进行调试和故障排查。
# 8)可扩展性和可维护性约束：
# 代码应具有良好的结构和注释，以便其他开发人员能够轻松理解和修改。
# 应提供清晰的接口和文档，以便将模型集成到其他系统中。
# 允许在不修改现有代码结构的情况下，通过配置或参数调整来适应不同的数据集和训练需求。
